#include "uefi.h"

// Author: you-call-this-a-project (foolish-shabby <2276316223@qq.com>)
// Just some POSIX wraps.
// 就一些 POSIX 包装

static FILE *__root_dir = NULL;

int putchar(char c)
{
    if (c == '\n') putchar('\r');
    wchar_t str[2] = {0, 0};
    str[0] = c;
    gST->ConOut->OutputString(gST->ConOut, str);
    return c;
}

void puts(char *s)
{
    printstring(s);
    putchar('\n');
}

int getchar(void)
{
    UINTN idx;
    gBS->WaitForEvent(1, &gST->ConIn->WaitForKey, &idx);
    EFI_INPUT_KEY key = {0};
    EFI_STATUS status = gST->ConIn->ReadKeyStroke(gST->ConIn, &key);
    int res = EFI_ERROR(status) ? 0 : key.UnicodeChar;
    if (res) putchar(res);
    int ret = res;
    while (res != '\r') {
        status = gST->ConIn->ReadKeyStroke(gST->ConIn, &key);
        res = EFI_ERROR(status) ? 0 : key.UnicodeChar;
        if (res) putchar(res);
    }
    putchar('\n');
    return ret;
}

// 为了追求简单（bushi）我们就写一个最简单的vsprintf，只支持%d %s %c %x
int vsprintf(char *str, const char *format, va_list ap)
{
    char *buf_ptr = str;
    const char *index_ptr = format;
    char index_char = *index_ptr;
    int32_t arg_int;
    char *arg_str;
    while (index_char) {
        if (index_char != '%') {
            *(buf_ptr++) = index_char;
            index_char = *(++index_ptr);
            continue;
        }
        index_char = *(++index_ptr);
        switch (index_char) {
            case 's':
                arg_str = va_arg(ap, char*);
                strcpy(buf_ptr, arg_str);
                buf_ptr += strlen(arg_str);
                index_char = *(++index_ptr);
                break;
            case 'c':
                *(buf_ptr++) = va_arg(ap, int);
                index_char = *(++index_ptr);
                break;
            case 'd':
                arg_int = va_arg(ap, int);
                if (arg_int < 0) {
                    arg_int = -arg_int;
                    *buf_ptr++ = '-';
                }
                itoa(arg_int, &buf_ptr, 10);
                index_char = *(++index_ptr);
                break;
            case 'x':
                arg_int = va_arg(ap, int);
                itoa(arg_int, &buf_ptr, 16);
                index_char = *(++index_ptr);
                break;
        }
    }
    return (int) strlen(str);
}

int sprintf(char *buf, const char *fmt, ...)
{
    va_list args;
    int retval;
    va_start(args, fmt);
    retval = (int) vsprintf(buf, fmt, args);
    va_end(args);
    return retval;
}


int vprintf(const char *fmt, va_list args)
{
    char *buf = (char *) calloc(sizeof(char), 8192);
    int ret = vsprintf(buf, fmt, args);
    printstring(buf);
    free(buf);
    return ret;
}

int printf(const char *fmt, ...)
{
    va_list args;
    va_start(args, fmt);
    int ret = vprintf(fmt, args);
    va_end(args);
    return ret;
}

FILE *fopen(const char *filename, const char *modes)
{
    EFI_GUID sfsp_guid = EFI_SIMPLE_FILE_SYSTEM_PROTOCOL_GUID;
    EFI_FILE_INFO info;
    EFI_GUID info_guid = EFI_FILE_INFO_GUID;
    UINTN fsize = (UINTN) sizeof(EFI_FILE_INFO);
    if (!__root_dir && LIP) {
        EFI_STATUS status = gBS->HandleProtocol(LIP->DeviceHandle, &sfsp_guid, (void **) &SFSP);
        if (!EFI_ERROR(status)) SFSP->OpenVolume(SFSP, &__root_dir);
    }
    if (!__root_dir) {
        return NULL;
    }
    wchar_t *wcname = (wchar_t *) calloc(sizeof(wchar_t), strlen(filename));
    c8stowcs(filename, wcname);
    FILE *ret = (FILE *) malloc(sizeof(FILE));
    UINTN mode = 0;
    // only support w, a, r
    if (modes[0] == 'w' || modes[0] == 'a') mode = EFI_FILE_MODE_WRITE | EFI_FILE_MODE_READ | EFI_FILE_MODE_CREATE;
    else mode = EFI_FILE_MODE_READ; 
    EFI_STATUS status = __root_dir->Open(__root_dir, &ret, wcname, mode, 0);
    if (EFI_ERROR(status)) {
        free(ret);
        return NULL;
    }

    status = ret->GetInfo(ret, &info_guid, &fsize, &info);
    if (EFI_ERROR(status)) {
        free(ret);
        return NULL;
    }

    if (modes[0] == 'a') fseek(ret, 0, SEEK_END);
    if (modes[0] == 'w') {
        // 截断
        info.FileSize = 0;
        ret->SetInfo(ret, &info_guid, fsize, &info);
    }
    return ret;
}

int fseek(FILE *stream, off_t offset, int whence)
{
    EFI_STATUS status;
    EFI_GUID info_guid = EFI_FILE_INFO_GUID;
    EFI_FILE_INFO info;
    UINTN fsize = sizeof(EFI_FILE_INFO);
    off_t off = 0;
    if (!stream || (whence < 0) || (whence > 2)) {
        return -1;
    }
    
    switch (whence) {
        case SEEK_SET:
            status = stream->SetPosition(stream, offset);
            break;
        case SEEK_CUR:
            status = stream->GetPosition(stream, &off);
            if (!EFI_ERROR(status)) {
                off += offset;
                status = stream->SetPosition(stream, off);
            }
            break;
        case SEEK_END:
            status = stream->GetInfo(stream, &info_guid, &fsize, &info);
            if (!EFI_ERROR(status)) {
                off = info.FileSize + offset;
                status = stream->SetPosition(stream, off);
            }
            break;
        default:
            status = EFI_UNSUPPORTED;
            break;
    }
    return EFI_ERROR(status) ? -1 : 0;
}

long int ftell(FILE *stream)
{
    off_t off = 0;
    EFI_STATUS status;

    if (!stream) {
        return -1;
    }

    status = stream->GetPosition(stream, &off);
    return EFI_ERROR(status) ? -1 : (long int) off;
}

int fflush(FILE *stream)
{
    EFI_STATUS status;
    if (!stream) {
        return 0;
    }
    status = stream->Flush(stream);
    return !EFI_ERROR(status);
}

int fclose(FILE *stream)
{
    EFI_STATUS status;
    if (!stream) {
        return 0;
    }
    status = stream->Close(stream);
    free(stream);
    return !EFI_ERROR(status);
}

void rewind(FILE *stream)
{
    fseek(stream, 0, SEEK_SET);
}

size_t fread(void *ptr, size_t size, size_t n, FILE *stream)
{
    UINTN bs = size * n;
    EFI_STATUS status = EFI_SUCCESS;
    int i;
    if (!stream || !ptr || size < 1 || n < 1) {
        return 0;
    }
    
    status = stream->Read(stream, &bs, ptr);
    if (EFI_ERROR(status)) return 0;
    return bs / size;
}

size_t fwrite(const void *ptr, size_t size, size_t n, FILE *stream)
{
    UINTN bs = size * n;
    EFI_STATUS status = EFI_SUCCESS;
    if (!stream || !ptr || size < 1 || n < 1) {
        return 0;
    }
    status = stream->Write(stream, &bs, (void *) ptr);
    fflush(stream);
    if (EFI_ERROR(status)) return 0;
    return bs / size;
}

int fgetpos(FILE *stream, fpos_t *position)
{
    if (!stream) return 0;
    EFI_STATUS status;
    status = stream->GetPosition(stream, position);
    return !EFI_ERROR(status) ? 0 : -1;
}

int fsetpos(FILE *stream, const fpos_t *position)
{
    if (!stream) return 0;
    EFI_STATUS status;
    status = stream->SetPosition(stream, *(fpos_t *) position);
    return !EFI_ERROR(status) ? 0 : -1;
}

int fputc(int character, FILE *stream)
{
    if (!stream) return -1;
    char ch = (char) character;
    char *str = (char *) calloc(sizeof(char), 2);
    str[0] = ch;
    int ret = fwrite(str, sizeof(char), 2, stream);
    return ret == 0 ? -1 : 0;
}

int putc(int character, FILE *stream)
{
    if (!stream) return -1;
    char ch = (char) character;
    char *str = (char *) calloc(sizeof(char), 2);
    str[0] = ch;
    int ret = fwrite(str, sizeof(char), 2, stream);
    return ret == 0 ? -1 : 0;
}

int fputs(const char *str, FILE *stream)
{
    if (!stream) return -1;
    int ret = fwrite(str, sizeof(char), strlen(str), stream);
    return ret == 0 ? -1 : strlen(str);
}

int vfprintf(FILE *stream, const char *fmt, va_list arg)
{
    char *buf = (char *) calloc(sizeof(char), 8192);
    int ret = vsprintf(buf, fmt, arg);
    fwrite(buf, sizeof(char), strlen(buf), stream);
    return ret;
}

int fprintf(FILE *stream, const char *fmt, ...)
{
    va_list args;
    va_start(args, fmt);
    int ret = vfprintf(stream, fmt, args);
    va_end(args);
    return ret;
}

int fgetc(FILE *stream)
{
    if (!stream) return -1;
    char *str = (char *) calloc(sizeof(char), 1);
    int ret = fread(str, sizeof(char), 1, stream);
    return ret == 0 ? -1 : *str;
}

int getc(FILE *stream)
{
    if (!stream) return -1;
    char *str = (char *) calloc(sizeof(char), 1);
    int ret = fread(str, sizeof(char), 1, stream);
    return ret == 0 ? -1 : *str;
}

int feof(FILE *stream)
{
    if (!stream) return 0;
    off_t off = 0;
    EFI_GUID info_guid = EFI_FILE_INFO_GUID;
    EFI_STATUS status;
    UINTN fsize = (UINTN) sizeof(EFI_FILE_INFO);
    EFI_FILE_INFO info;
    status = stream->GetPosition(stream, &off);
    if (EFI_ERROR(status)) return -1;
    status = stream->GetInfo(stream, &info_guid, &fsize, &info);
    if (EFI_ERROR(status)) return -1;
    stream->SetPosition(stream, off);
    return info.FileSize == off;
}

char *fgets(char *str, int n, FILE *stream)
{
    if (!stream) return NULL;
    for (int i = 0; i < n; i++) {
        if (feof(stream)) return str;
        char ch = fgetc(stream);
        if (ch == '\n') return str;
        str[i] = ch;
    }
    return str;
}